using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using fit;
using fitnesse;
using fitnesse.responders.run;
using fitnesse.slim;
using fitnesse.trinidad;
using fitnesse.wiki;
using fitnesseMain;
using log4net;
using NDesk.Options;
using StreamReader=System.IO.StreamReader;

namespace FitNesse4Net
{
    /// <summary>
    /// Gateway class to Java FitNesse classes. Allows to run a single test, test suite, shutdown a server and run FitNesse using standard arguments.
    /// </summary>
    public class FitNesse : IFitNesse
    {
        private static readonly ILog log = LogManager.GetLogger(typeof (FitNesse));

        public string WorkDirectoryPath { get; set; }

        public string TargetDirectory { get; set; }

        public static List<Assembly> ExternalAssemblies { get; private set; }

        public void RunTestSuite(string testSuiteName)
        {
            Directory.SetCurrentDirectory(WorkDirectoryPath);

            InitializeExternalAssemblies();

            log.InfoFormat("Running test suite {0} ...", testSuiteName);

            var repository = new FitNesseRepository(WorkDirectoryPath);

            if (!Directory.Exists(TargetDirectory))
            {
                Directory.CreateDirectory(TargetDirectory);
            }

            var resultsReporitory = new FolderTestResultRepository(TargetDirectory);
            repository.prepareResultRepository(resultsReporitory);

            var suiteResult = new SuiteResult(testSuiteName);
            var testDescriptors = repository.getSuite(testSuiteName);
            for (var i = 0; i < testDescriptors.size(); i++)
            {
                var testResult = RunTest((TestDescriptor) testDescriptors.get(i));
                suiteResult.append(testResult);
                resultsReporitory.recordTestResult(testResult);
            }
            resultsReporitory.recordTestResult(suiteResult);
        }

        public void RunTest(string testName)
        {
            Directory.SetCurrentDirectory(WorkDirectoryPath);
            
            InitializeExternalAssemblies();

            var repository = new FitNesseRepository(WorkDirectoryPath);

            if (!Directory.Exists(TargetDirectory))
            {
                Directory.CreateDirectory(TargetDirectory);
            }

            var resultsReporitory = new FolderTestResultRepository(TargetDirectory);
            repository.prepareResultRepository(resultsReporitory);

            var testDescriptor = repository.getTest(testName);

            RunTest(testDescriptor);
        }

        public void StopServer(int port)
        {
            Shutdown.main(new[] { "-p", port.ToString() });
        }

        public void ParseArgumentsAndRun(params string[] args)
        {
            Directory.SetCurrentDirectory(WorkDirectoryPath);
            
            FitNesseMain.main(args);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args">--test-runner [--log-level=DEBUG|INFO|...] pathToAssembly1;pathToAssembly2 port</param>
        public void StartTestRunner(params string[] args)
        {
            Directory.SetCurrentDirectory(WorkDirectoryPath);
            
            // parse second argument from the back
            var assemblyPathsToLoad = args.Reverse().Skip(1).First().Split(Path.PathSeparator);

            ExternalAssemblies = new List<Assembly>();
            foreach (var assemblyPath in assemblyPathsToLoad)
            {
                log.DebugFormat("Loading assembly {0} ...", assemblyPath);

                ExternalAssemblies.Add(Assembly.LoadFrom(assemblyPath));
            }

            var slimFactory = new DotNetSlimFactory();
            DotNetSlimService.Execute(slimFactory, args);

            while(!slimFactory.IsStopped)
            {
                Thread.Sleep(100);
            }
        }

        private class PageData : fitnesse.wiki.PageData {
            public TestDescriptor TestDescriptor { get; set; }

            public PageData(WikiPage page) : base(page)
            {
            }

            public override string getHtml()
            {
                return TestDescriptor.getContent();
            }
        }

        private SingleTestResult RunTest(TestDescriptor testDescriptor)
        {
            log.InfoFormat("Running test {0} ...", testDescriptor.getName());

            var wikiPage = new InMemoryPage(testDescriptor.getName(), testDescriptor.getContent(), null);
            var listener = new DotNetTestSystemListener();
            var slim = new DotNetHtmlSlimTestSystem(wikiPage, listener);
            var testSystemDescriptor = TestSystem.getDescriptor(wikiPage.getData(), false);
            testSystemDescriptor.testRunner = "fitnesse.slim.SlimService"; // not used

            slim.setFastTest(true);
            slim.getExecutionLog("", testSystemDescriptor);
            slim.start();

            var pageData = new PageData(wikiPage) {TestDescriptor = testDescriptor};
            var htmlString = slim.runTestsAndGenerateHtml(pageData);
            slim.bye();
            
            var testSummary = slim.getTestSummary();

            log.InfoFormat("Test results: {0} right, {1} wrong, {2} ignored, {3} exceptions",
                           testSummary.right, testSummary.wrong, testSummary.ignores, testSummary.exceptions);

            using (
                TextWriter htmlFileWriter = new StreamWriter(TargetDirectory + Path.DirectorySeparatorChar + testDescriptor.getName() + ".html"))
            {
                htmlFileWriter.Write(htmlString);
            }

            return new SingleTestResult(new Counts(testSummary.right, testSummary.wrong, testSummary.ignores, testSummary.exceptions),
                                        testDescriptor.getName(), htmlString);
        }

        private void InitializeExternalAssemblies()
        {
            if(ExternalAssemblies == null)
            {
                ExternalAssemblies = new List<Assembly>();
            }
            else
            {
                return; // already initialized
            }

            // a bit ugly, directly access file containing paths to assemblies
            var textReader = new StreamReader(WorkDirectoryPath + Path.DirectorySeparatorChar + "FitNesseRoot" + Path.DirectorySeparatorChar + "content.txt");

            while (!textReader.EndOfStream)
            {
                var line = textReader.ReadLine();

                if (!line.StartsWith("!path"))
                {
                    continue;
                }

                var assemblyPath = line.Substring(6); // skip '!path '

                log.DebugFormat("Loading assembly {0} ...", assemblyPath);

                ExternalAssemblies.Add(Assembly.LoadFrom(assemblyPath));
            }
        }
    }
}